import abc
import json
import sys
import traceback
from dataclasses import dataclass
from datetime import datetime
from enum import Enum
from typing import Union,Callable

class ReportEventType(Enum):

    NORMAL = 1
    LOG = 2
    TIME = 3
    ASSERT = 4

@dataclass
class ReportEvent:

    name:str
    type:ReportEventType
    time:datetime

    sub_type:int  = 0  # 子类型
    msg:str = None  ##消息内容
    exception:str = None ## 堆栈信息
    seconds:float = 0 ## 耗时秒数

class IReporter(object):

    def report(self, event: ReportEvent):
        """
        上报花费时间。
        """
        pass

__default_impl = None

def set_default(report:IReporter):
    """
    设置默认的report实现器。
    :param report:
    :return:
    """
    global __default_impl
    __default_impl = report

def _default() -> IReporter:
    global __default_impl
    if __default_impl is None:
        from emi.report._impl import _ConsoleReporter
        __default_impl = _ConsoleReporter()
    return __default_impl

def _get_call_stack_info():
    stack_list = traceback.format_stack()
    if len(stack_list) > 2:
        stack_list.pop()
        stack_list.pop()
        return "".join(stack_list)
    return None

class TimeEvent:

    def __init__(self,name:str,msg:str,seoconds = None):
        self.start = datetime.now()
        self.name = name
        self.msg = msg
        self._has_report = False
        self._seoconds = seoconds

    def get_duration(self)->float:
        """
        返回时长。
        :return:
        """
        if self._seoconds is None:
            return (datetime.now() - self.start).total_seconds()
        return self._seoconds

    def report_if_over(self,seconds:float,print_exc = True):
        """
        如果时长超过seconds，则上报
        :param seconds:
        :return:
        """
        if self._has_report:
            raise RuntimeError("has report before")
        self._has_report = True
        _duration = self.get_duration()
        if _duration > seconds:
            event = ReportEvent(name=self.name,type=ReportEventType.TIME,time=datetime.now(),msg=self.msg,seconds=_duration)
            if print_exc:
                event.exception = _get_call_stack_info()
            _default().report(event)

    def report(self,print_exc = False):
       """

       :param print_exc:  是否包含调用堆栈信息。
       :return:
       """
       self.report_if_over(seconds=-1,print_exc=print_exc)

def log_debug(msg:str,tag:str = None):
    event = ReportEvent(name=tag if tag else "",
                        type=ReportEventType.LOG,
                        time=datetime.now(),
                        sub_type=100,
                        msg=msg,
                        )
    _default().report(event)

def log_info( msg: str,tag:str=None):
    """
    上报日志信息。
    :param msg: 日志内容。
    :param tag: 标签类型
    :return:
    """
    event = ReportEvent(name=tag if tag else "",
                        type=ReportEventType.LOG,
                        time=datetime.now(),
                        sub_type=200,
                        msg=msg,
                        )
    _default().report(event)


def log_warn( msg: str,tag:str=None,print_exc = False):
    """
    上报日志信息。
    :param msg: 日志内容。
    :param tag: 标签类型
    :param print_exc:默认的如果有异常发生自动会包含堆栈，也可以设为True强制包含调用堆栈信息
    :return:
    """
    except_info_list = traceback.format_exception(*sys.exc_info(), limit=None, chain=None)
    has_occur_except = len(except_info_list) > 1
    except_info = "".join(except_info_list) if has_occur_except else None
    if not has_occur_except and print_exc:
        except_info = _get_call_stack_info()
    event = ReportEvent(name=tag if tag else "",
                        type=ReportEventType.LOG,
                        time=datetime.now(),
                        sub_type= 300,
                        msg= msg,
                        exception= except_info
                        )
    _default().report(event)

def log_error( msg: str,tag:str=None,print_exc = False):
    """
    上报日志信息。
    :param msg: 日志内容。
    :param tag: 标签类型
    :param print_exc:默认的如果有异常发生自动会包含堆栈，也可以设为True强制包含调用堆栈信息
    :return:
    """
    except_info_list = traceback.format_exception(*sys.exc_info(), limit=None, chain=None)
    has_occur_except = len(except_info_list) > 1
    except_info = "".join(except_info_list) if has_occur_except else None
    if not has_occur_except and print_exc:
        except_info = _get_call_stack_info()
    event = ReportEvent(name=tag if tag else "",
                        type=ReportEventType.LOG,
                        time=datetime.now(),
                        sub_type=400,
                        msg=msg,
                        exception=except_info
                        )
    _default().report(event)

def event(name:str,data:{} = None):
    """
    上报普通事件
    :param name:  事件名称
    :param data:  事件数据,可以将转化为json值。
    :return:
    """
    json_msg = None
    if data:
        j = json.dumps(data)
        json_msg = str(j)
    event = ReportEvent(name=name,
                        type=ReportEventType.NORMAL,
                        time=datetime.now(),
                        msg=json_msg,
                        )
    _default().report(event)


def assert_that(flag: bool, msg: Union[str,Callable[[],str]], print_exc=True):
    """
    上报判定事件。
    :param flag: 判定表达
    :param msg: 消息内容,也可以用callbale的方式会返回
    :param print_exc:是否包含调用堆栈信息
    :return:
    """
    if not flag:
        if msg is not None and not isinstance(msg,str):
            msg_callbale:Callable[[],str] = msg
            msg =  msg_callbale()
        event = ReportEvent(name="",
                            type=ReportEventType.ASSERT,
                            time=datetime.now(),
                            msg=msg,
                            )
        if print_exc:
            event.exception = _get_call_stack_info()
        _default().report(event)


def time_event(name: str, seconds:float,msg = None):
    """
    上报耗时事件。
    :param name: 事件名称
    :param seconds: 时长，单位秒
    :return:
    """
    _time_event_ = TimeEvent(name=name,msg=msg,seoconds=seconds)
    _time_event_.report()

def start_time_event(name,msg:str = None)->TimeEvent:
    """
    开始一个时长上报
    :param name:
    :param msg:
    :return:
    """
    return TimeEvent(name=name,msg=msg)







